package fi.iki.joker.mobilogger.blogs;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;

import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Xml;
import android.widget.EditText;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;
import fi.iki.joker.mobilogger.db.BlogConfigBean;
import fi.iki.joker.mobilogger.db.DBUtil;
import fi.iki.joker.mobilogger.util.AlertUtil;
import fi.iki.joker.mobilogger.util.StreamUtil;

public class GoogleBloggerHttpsApi implements BlogInterface, ContentHandler {

	private HttpClient client;
	private final String TAG = "GoogleBloggerHttpsApi";
	private final String ENT_MIME = "application/x-www-form-urlencoded";
	private final String ENT_ATOM = "application/atom+xml";
	private final String GOOGLE_CLIENTLOGIN_URL = "https://www.google.com/accounts/ClientLogin";
	private final String BLOGGER_PROFILE_URL = "http://www.blogger.com/feeds/default/blogs";
	private final String ISO_ENC = "ISO-8859-1";
	private final String APPNAME=BlogConfigConstants.APPNAME;
	private final String SERVICENAME = "blogger";
	private final int CREATED_OK_HTTP_CODE = 201;
	private final int READ_OK_HTTP_CODE = 200;
	private final int CONFIG_URL_ID = 101;
	private Activity parentRef = null;
	//SAX parsing state vars + data:
	private boolean inEntry,inTitle,inLink,foundTitle, foundHref = false;
	private String currentHref, currentRel, currentTitle;
	private boolean fullyParsed = false;
	private HashMap<String,String> postUrls = null;
	
	public CharSequence getConfigEditorData() {
		// TODO Auto-generated method stub
		// return URL field:
		EditText urlField = (EditText)(parentRef.findViewById(CONFIG_URL_ID));
		if(urlField == null) {
			Log.e(TAG,"Can't fetch the URL field in blog config editor from Blogger HTTPS api!");
		} else {
			return urlField.getText().toString();
		}
		return null;
	}

	
	
	public void setInstanceConfig(CharSequence config) {
		// TODO Auto-generated method stub
		
	}



	/**
	 * This is the editable blog post url field
	 */
	
	public static class MyUrlText extends EditText {
        private Rect mRect;
        private Paint mPaint;

        // we need this constructor for ViewInflate
        public MyUrlText(Context context, AttributeSet attrs) {
            super(context, attrs);

            mRect = new Rect();
            mPaint = new Paint();
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setColor(0xFF0000FF);
        }

        public MyUrlText(Context context) {
            super(context);

            mRect = new Rect();
            mPaint = new Paint();
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setColor(0xFF0000FF);
        }
        
        @Override
        protected void onDraw(Canvas canvas) {

            int count = getLineCount();
            Rect r = mRect;
            Paint paint = mPaint;

            for (int i = 0; i < count; i++) {
                int baseline = getLineBounds(i, r);

                canvas.drawLine(r.left, baseline + 1, r.right, baseline + 1,
                                paint);
            }

            super.onDraw(canvas);
        }
    }
	
	protected GoogleBloggerHttpsApi() {
		client = new DefaultHttpClient();
	}
	
	public String getAuthId(String username, String password) {
		/*RequestEntity emailent = null;
		RequestEntity passwdent = null;
		RequestEntity sourceent = null;
		RequestEntity serviceent = null;*/
		String res = null;
		int rescode = 0;
		HttpResponse response;
		HttpEntity responseEntity=null;
		HttpPost httpsPost = new HttpPost(GOOGLE_CLIENTLOGIN_URL);
		List <NameValuePair> pairs = new ArrayList <NameValuePair>();
		pairs.add(new BasicNameValuePair("accountType", "GOOGLE"));
		pairs.add(new BasicNameValuePair("Email", username));
		pairs.add(new BasicNameValuePair("Passwd", password));
		pairs.add(new BasicNameValuePair("service", this.SERVICENAME));
		pairs.add(new BasicNameValuePair("source", this.APPNAME));
		try {
			httpsPost.setEntity(new UrlEncodedFormEntity(pairs));
		} catch (UnsupportedEncodingException e1) {
			Log.e(TAG, "Unsupported encoding using while HttpsPost");
		}
		try {
				response = client.execute(httpsPost);
				rescode = response.getStatusLine().getStatusCode();
				Log.d(TAG,"Response code: "+rescode);
				responseEntity = httpsPost.getEntity();
				if(responseEntity == null) {
					Log.d(TAG,"Response entity was null!");
					throw new HttpException("The response from post call was null!");
				} else {
					Log.d(TAG,"Response body len: "+responseEntity.getContentLength());
					res=StreamUtil.convertStreamToString(responseEntity.getContent());
				}
			} catch (HttpException he) {
				Log.e(TAG,"HTTPException during client comms:"+he.getMessage());
			} catch (IOException ie) {
				Log.e(TAG,"IOException during client comms:"+ie.getMessage());
			} finally {
				Log.d(TAG,"Executed POST request to Google without exceptions.");
				try {
					responseEntity.consumeContent();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		if(rescode == java.net.HttpURLConnection.HTTP_OK) {
			Log.e(TAG,"HTTP status OK, returning auth string:"+res);
			return getAuthToken(res);
		} else {
			Log.e(TAG,"HTTP status not OK: "+rescode+" Returning null...");
			return null;
		}
	}
	
	/**
	 * Fetch a list of blogs that match the user's credentials
	 */
	
	public BloggerFeedEntryBean[] getDefaultBlogsMetafeed() {
		BloggerFeedEntryBean[] res = null;
		return res;
	}
	/**
	 * creates a post using the xml atom api
	 * @param authToken the token fetched using getAuthId
	 * @param titleType the content type of title, defaults to "xhtml"
	 * @param title Content of the post's title
	 * @param contentType Content type of the post, defaults to "xhtml"
	 * @param content
	 * @param authorName
	 * @param authorEmail
	 * @param isDraft
	 * @return
	 */
	
	public boolean createPost(Activity parent,
						String authToken, 
						String postUrl,
						String titleType,
						String title, 
						String contentType, String content,
						String authorName, String authorEmail, 
						boolean isDraft) {
		if(titleType == null) {
			titleType = "xhtml";
		}
		if(contentType == null) {
			contentType = "xhtml";
		}
		int rescode = 0;
		String res = null;
		// if caller does not provide us a url to post the entry,
		// then we get it from the getPostUrl method.
		// With the Blogger HTTP api, this is a very typical case.
		this.parentRef = parent;
		if(postUrl == null) {
			postUrl = getPostUrl(authToken);
		} else if(postUrl.length() == 0) {
			postUrl = getPostUrl(authToken);
		}
		//if even after the getPostUrl call we still have a null 
		//URL, let's call it a day and go home.
		if(postUrl == null) {
			Log.e(TAG,"Post URL still null after getPostUrl call!");
			if(parent != null) {
				AlertUtil.showAlert(parent,"Publishing error","Unable to read the URL to which the blog entry should be created.");
				return false;
			} else {
				return false;
			}
		}
		Log.d(TAG,"Post method URL: "+postUrl);
		//When using this classes "client", it says "read time out"
		//@TODO investigate, for now, use a new instance
		HttpClient myClient = new DefaultHttpClient();
		HttpPost publishPost = new HttpPost(postUrl);
		HttpEntity ent = null;
		HttpResponse response = null;
		try {
		ent = new StringEntity(createEntryAtomElement(authToken, titleType, 
				title, contentType, content, authorName, authorEmail, isDraft), ISO_ENC);
		} catch (UnsupportedEncodingException ue) {
			Log.e(TAG,"Unsupported encoding when setting entry content to POST request");
			return false;
		}
		Header authHeader = new BasicHeader("Authorization", "GoogleLogin auth="+authToken);
		
		//publishPost.setContentChunked(false);
		//publishPost.setContentLength(ent.getContentLength());
	
		publishPost.setEntity(ent);
		publishPost.setHeader(authHeader);
		try {
			response = myClient.execute(publishPost);
			if(response != null && response.getStatusLine() != null && publishPost.getEntity().getContent() != null) {
				rescode = response.getStatusLine().getStatusCode();
				res=StreamUtil.convertStreamToString(publishPost.getEntity().getContent());
			}
			Log.d(TAG,"Response code: "+rescode);
			Log.d(TAG,"Response body: "+res);
			
		} catch (IOException ie) {
			Log.e(TAG,"IOException during client comms:"+ie.getMessage());
			throw new RuntimeException(ie);
		} finally {
			Log.d(TAG,"Executed POST request to Google without exceptions.");
			//publishPost.creleaseConnection();
		}
		if(rescode == CREATED_OK_HTTP_CODE) {
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Parses the authentication token from the authentication response.
	 * @param authResponse
	 * @return String, the Auth=[x] string
	 */
	
	private String getAuthToken(String authResponse) {
		String matchPattern = "[\\=]";
		if(authResponse != null) {
			Log.d(TAG,"Response matches pattern "+matchPattern);
			String [] matches = authResponse.split(matchPattern);
			for(int i = 0; i < matches.length; i++) {
				Log.d(TAG,"Match #"+i+" is "+matches[i]);
			}
			try {
				return matches[3].trim();
			} catch (ArrayIndexOutOfBoundsException e) {
				Log.e(TAG,"Error in matching logic in getAuthToken!");
				return null;
			}
		} else {
			Log.e(TAG,"NULL authResponse passed to getAuthToken!");
			return null;
		}
	}
	
	private String createEntryAtomElement(String authToken, String titleType,
						String title, 
						String contentType, String content,
						String authorName, String authorEmail,
						boolean isDraft) {
		StringBuffer res = new StringBuffer();
		res.append("<entry xmlns = 'http://www.w3.org/2005/Atom'>\n");
		res.append("<title type='"+titleType+"'>"+title+"</title>");
		if(isDraft) {
			res.append("<app:control xmlns:app='http://purl.org/atom/app#'>");
		    res.append("<app:draft>yes</app:draft>");
		    res.append("</app:control>");
		}
		res.append("<content type='"+contentType+"'>");
		res.append("<div xmlns=\"http://www.w3.org/1999/xhtml\">");
		res.append(content);
		res.append("</div>");
		res.append("</content>");
		res.append("<author>");
		res.append("<name>"+authorName+"</name>");
		res.append("<email>"+authorEmail+"</email>");
		res.append("</author>");
		res.append("</entry>");
		return res.toString();
	}
	
	public String getPostUrl(String authToken) {
		postUrls = new HashMap<String,String>();
		HttpGet getBlogs = new HttpGet(BLOGGER_PROFILE_URL);
		HttpResponse response = null;
		Header authHeader = new BasicHeader("Authorization", "GoogleLogin auth="+authToken);
		getBlogs.setHeader(authHeader);
		String res = null;
		int rescode = 0;
		try {
			response = client.execute(getBlogs);
			if(response.getStatusLine() != null && response.getEntity() != null) {
				rescode = response.getStatusLine().getStatusCode();
				HttpEntity ent = response.getEntity();
				String content = StreamUtil.convertStreamToString(ent.getContent());
				Log.d(TAG,"Response code: "+rescode);
				Log.d(TAG,"Response body: "+content);
				res=content;
			}
		} catch (IOException ie) {
			Log.e(TAG,"IOException during client comms:"+ie.getMessage());
		} finally {
			Log.d(TAG,"Executed POST request to Google without exceptions.");
			//getBlogs.releaseConnection();
		}
		if(rescode == READ_OK_HTTP_CODE) {
			try {
				Xml.parse(res, this);
			} catch (SAXException e) {
				Log.e(TAG,"Parse error ("+e.getMessage()+") while parsing response!");
			}
			//if user has multiple blogs in the metafeed, we need to select
			//one from them so that we know what's the correct post url
			if(fullyParsed) {
				if(postUrls.size() == 1) {
					Set<String> keySet = postUrls.keySet();
					Iterator<String> iter = keySet.iterator();
					while(iter.hasNext()) {
						String key = iter.next();
						Log.d(TAG,"Parsed result has only one URL good for posting, return that...");
						String purl = postUrls.get(key);
						Log.d(TAG,"Going to post to "+purl);
						return purl;
					}
				} else {
					Log.d(TAG, "User has "+postUrls.size()+" blogs configured. Make him select.");
					// user needs to select which url to use.
					// not implemented yet! (we have the blogger API working so this is not a top priority)
					return null;
				}
				
			} else {
				Log.e(TAG,"Reached the post URL lookup and parser indicates that the full document has not been parsed yet!");
				return null;
			}
		} else {
			Log.e(TAG,"HTTP status indicates that the blogger metafeed could not be read properly!");
			return null;
		}
		Log.e(TAG,"End of the post URL generation method reached, should not be possible.");
		return null;
	}
	
	public void createConfigEditorRows(Activity parent, TableLayout tl,
			Context context, int startIdFrom, int populateFromId) {
		String configURLText = null;
		this.parentRef = parent;
		if(populateFromId > 0) {
			DBUtil db = new DBUtil();
			BlogConfigBean tempConfigBean = null;
			if(db.isDatabaseReady(parentRef)) {
				 tempConfigBean = db.getBlogConfigById(populateFromId);
			}
			configURLText = tempConfigBean.getPostConfig().toString();
		} else {
			configURLText = "";
		}
		TableRow tr1 = new TableRow(context);
		TableRow tr2 = new TableRow(context);
    	tr1.setId(startIdFrom);
    	tr2.setId(startIdFrom+1);
		TextView title = new TextView(context);
		TextView help = new TextView(context);
		// Create field to read url from user
		MyUrlText urlField = new MyUrlText(context);
		title.setText("Google Blogger userID");
		help.setText("ID is the sequence \nof numbers in URL: \nhttp://www.blogger.com/\nprofile/[userID]");
		urlField.setId(CONFIG_URL_ID);
		urlField.setText(configURLText);
		TableRow.LayoutParams firstRowLayoutParams = new TableRow.LayoutParams(); 
		TableRow.LayoutParams secondRowLayoutParams = new TableRow.LayoutParams(); 
		secondRowLayoutParams.width = TableRow.LayoutParams.WRAP_CONTENT;
		tr1.addView(title, firstRowLayoutParams);
		tr1.addView(urlField, firstRowLayoutParams);
		tr2.addView(help, secondRowLayoutParams);
		tl.addView(tr1, new TableLayout.LayoutParams());
		tl.addView(tr2, new TableLayout.LayoutParams());
	}
	
	/**
	 * This is used to present a dialog to the user in case user has multiple blogs to choose from.
	 * 
	 */
	
	private void getTextInputFromDialog() {
    	//this should make the user select the blog of choise
    }
	
	OnClickListener okButtonListener = new OnClickListener() {
        public void onClick(DialogInterface df, int item) {
        		
        }
	};
	// start the SAX parsing stuff.

	public void characters(char[] ch, int start, int length) throws SAXException {
		String chars = (new String(ch).substring(start, start + length));
		if(inEntry && inTitle) {
			currentTitle = chars;
			foundTitle = true;
		}
	}

	public void endElement(String uri, String name, String qName)
			throws SAXException {
		if(name.trim().equals("entry")) {
			this.inEntry = false;
			this.inLink = false;
			this.inTitle = false;
			foundHref = false;
			foundTitle = false;
			currentTitle = null;
			currentHref = null;
			currentRel = null;
		}
		if(name.trim().equals("link")) {
			inLink = false;
		}
		
		if(name.trim().equals("title")) {
			inTitle = false;
		}
	}

	public void startElement(String uri, String name, String qName,
			Attributes attrs) throws SAXException {
		if(name.trim().equals("entry")) {
			this.inEntry = true;
		}
		if(name.trim().equals("title")) {
			this.inTitle = true;
		}
		if(name.trim().equals("link")) {
			this.inLink = true;
		}
		// If we are in an entry element and in an link element,
		// check if the link element carries a "rel" attribute which 
		// means that the "href" attribute contains the post url (i.e something like: 
		// <link rel='http://schemas.google.com/g/2005#post'
	    // type='application/atom+xml'
	    //    href='http://www.blogger.com/feeds/blogID/posts/default'>
	    //  </link>
		if(inEntry && inLink) {
			currentRel = attrs.getValue("rel");
			currentHref = attrs.getValue("href");
			if((currentRel != null) && (currentRel.trim().endsWith("#post")) 
					&& (currentHref != null) && (currentHref.length() > 0)) {
				//indicates we have a posting url
				postUrls.put(currentTitle, currentHref);
			}
			
		}
	}

	public void endDocument() throws SAXException {
		fullyParsed = true;
	}

	public void endPrefixMapping(String prefix) throws SAXException {
		// TODO Auto-generated method stub
		
	}

	public void ignorableWhitespace(char[] ch, int start, int length)
			throws SAXException {
		// TODO Auto-generated method stub
		
	}

	public void processingInstruction(String target, String data)
			throws SAXException {
		// TODO Auto-generated method stub
		
	}

	public void setDocumentLocator(Locator locator) {
		// TODO Auto-generated method stub
		
	}

	public void skippedEntity(String name) throws SAXException {
		// TODO Auto-generated method stub
		
	}

	public void startDocument() throws SAXException {
		// TODO Auto-generated method stub
		
	}
	
	public void startPrefixMapping(String yks, String kaks) throws SAXException {
		
	}
	
}
